﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Ecodex.RestAPI.Exceptions;
using System.IO;
using Newtonsoft.Json.Linq;
using Ecodex.RestAPI.Models;
using System.Security.Cryptography;

namespace Ecodex.RestAPI
{
    public class RestApi
    {
        const string RouteToken = "Token?version=2";
        const string GrandType = "authorization_token";
        const string RouteDocumentos = "api/Documentos";
        const string RouteClaveCertificado = "api/Certificados/Clave";
        const string HeaderAuthToken = "X-Auth-Token";

        public string BaseAddress { get; private set; }

        public RestApi(Uri serviceURI)
        {
            BaseAddress = serviceURI.ToString();
        }

        public Token GetToken(string rfc)
        {
            NameValueCollection values = new NameValueCollection();
            values.Add("rfc", rfc);
            values.Add("grant_type", GrandType);
            using (WebClient client = new WebClient())
            {
                try
                {
                    byte[] res = client.UploadValues(BaseAddress + RouteToken, values);
                    string json = Encoding.UTF8.GetString(res);

                    return JsonConvert.DeserializeObject<Token>(json);
                }
                catch (WebException ex)
                {
                    if (ex.Response == null)
                        throw new FallaAplicacion(ex.Message);
                    throw ControlException(ex);
                }
                catch (Exception ex)
                {
                    throw new FallaAplicacion(ex.Message);
                }
            }
        }

        public List<DocumentoInfo> GetDocumentos(Token token, string claveIntegrador, int limit = 100)
        {
            using (WebClient webClient = new WebClient())
            {
                try
                {
                    webClient.Headers[HttpRequestHeader.Authorization] = token.token_type + " " + token.access_token;
                    webClient.Headers[HeaderAuthToken] = GetSHA1(string.Format("{0}|{1}", claveIntegrador, token.service_token));
                    string json = webClient.DownloadString(BaseAddress + RouteDocumentos + string.Format("?limit={0}", limit));
                    List<DocumentoInfo> docs = JsonConvert.DeserializeObject<List<DocumentoInfo>>(json);
                    return docs;
                }
                catch (WebException ex)
                {
                    if (ex.Response == null)
                        throw new FallaAplicacion(ex.Message);
                    throw ControlException(ex);
                }
                catch (Exception ex)
                {
                    throw new FallaAplicacion(ex.Message);
                }
            }
        }

        public CertificadoClave GetClaveCertificado(Token token, string claveIntegrador)
        {
            using (WebClient webClient = new WebClient())
            {
                try
                {
                    webClient.Headers[HttpRequestHeader.Authorization] = token.token_type + " " + token.access_token;
                    webClient.Headers[HeaderAuthToken] = GetSHA1(string.Format("{0}|{1}", claveIntegrador, token.service_token));
                    string json = webClient.DownloadString(BaseAddress + RouteClaveCertificado);
                    return JsonConvert.DeserializeObject<CertificadoClave>(json);
                }
                catch (WebException ex)
                {
                    if (ex.Response == null)
                        throw new FallaAplicacion(ex.Message);
                    throw ControlException(ex);
                }
                catch (Exception ex)
                {
                    throw new FallaAplicacion(ex.Message);
                }
            }
        }

        public String GetDocumento(Token token, string claveIntegrador, string hash)
        {
            if (string.IsNullOrEmpty(hash))
            {
                throw new FallaValidacion("Es requerido especificar un valor para 'hash'");
            }
            using (WebClient webClient = new WebClient())
            {
                try
                {
                    webClient.Headers[HttpRequestHeader.Authorization] = token.token_type + " " + token.access_token;
                    webClient.Headers[HeaderAuthToken] = GetSHA1(string.Format("{0}|{1}", claveIntegrador, token.service_token));
                    string xml = webClient.DownloadString(BaseAddress + RouteDocumentos + "/" + hash);
                    return xml;
                }
                catch (WebException ex)
                {
                    if (ex.Response == null)
                        throw new FallaAplicacion(ex.Message);
                    throw ControlException(ex);
                }
                catch (Exception ex)
                {
                    throw new FallaAplicacion(ex.Message);
                }
            }
        }

        private Exception ControlException(WebException ex)
        {
            String responseString = StreamToString(ex.Response.GetResponseStream());
            var falla = JsonConvert.DeserializeObject<Error>(responseString);
            Type type = Type.GetType("Ecodex.RestAPI.Exceptions." + falla.error);
            if (type == typeof(FallaAplicacion))
                return new FallaAplicacion(falla.error_description, falla.error_code);
            else if (type == typeof(FallaSesion))
                return new FallaSesion(falla.error_description, falla.error_code);
            else if (type == typeof(FallaValidacion))
                return new FallaValidacion(falla.error_description, falla.error_code, falla.error_suggestion);

            return new FallaAplicacion(ex.Message);
        }

        private String StreamToString(Stream stream)
        {
            StreamReader reader = new StreamReader(stream, Encoding.UTF8);
            return reader.ReadToEnd();
        }

        private string GetSHA1(String str)
        {
            UTF8Encoding UE = new UTF8Encoding();
            byte[] hashValue;
            byte[] message = UE.GetBytes(str);

            using (SHA1Managed hashString = new SHA1Managed())
            {
                string hex = "";
                hashValue = hashString.ComputeHash(message);
                foreach (byte x in hashValue)
                {
                    hex += String.Format("{0:x2}", x);
                }
                return hex;
            }
        }

    }
}
